#ifndef __M_ROOM_H__
#define __M_ROOM_H__
#include "util.hpp"
#include "db.hpp"
#include "online.hpp"
#include <mutex>
#include <cassert>

#define BOARD_ROW 15
#define BOARD_COL 15

typedef enum
{
   GAME_START,
   GAME_OVER
} room_status;

#define chess_white 1
#define chess_black 2

class room
{
private:
   int play_count;                      // 在线的玩家数量
   uint64_t id;                         // 房间id
   room_status status;                  // 房间状态
   uint64_t white_id;                   // 白起的id
   uint64_t black_id;                   // 黑棋的id
   user_table *user_tb;                 // 使用在db.hpp中封装的类进行数据库操作，因为胜利后要进行修改数据库内容
   online_manager *online_user;         // 使用online.hpp中封装的类，因为需要在某一个人做出行动（下棋。聊天。退出）后把内容广播到整个room
   std::vector<std::vector<int>> board; // 棋盘

public:
   

   // 主要从外部传入的是room id ,dh.hpp中的数据库表操作对象，online.hpp中的连接管理对象！！！！！！
   room(uint64_t _id, user_table *_user_tb, online_manager *_online_user) : id(_id),
                                                                            user_tb(_user_tb),
                                                                            online_user(_online_user),
                                                                            status(GAME_START),
                                                                            play_count(0),
                                                                            board(BOARD_ROW, std::vector<int>(BOARD_COL, 0))
   {
      DBG_LOG("创建房间成功！！房间ID是 %d\n", id);
   }





   bool five(int row, int col, int row_off, int col_off, int color)
   {
      // row和col是下棋位置，  row_off和col_off是偏移量，也是方向
      int count = 1;
      int search_row = row + row_off;
      int search_col = col + col_off;
      while (search_row >= 0 && search_row < BOARD_ROW &&
             search_col >= 0 && search_col < BOARD_COL &&
             board[search_row][search_col] == color)
      {
         // 同色棋子数量++
         count++;
         // 检索位置继续向后偏移
         search_row += row_off;
         search_col += col_off;
      }
      search_row = row - row_off;
      search_col = col - col_off;
      while (search_row >= 0 && search_row < BOARD_ROW &&
             search_col >= 0 && search_col < BOARD_COL &&
             board[search_row][search_col] == color)
      {
         // 同色棋子数量++
         count++;
         // 检索位置继续向后偏移
         search_row -= row_off;
         search_col -= col_off;
      }
      return (count >= 5);
   }

   uint64_t check_win(int row, int col, int color)
   {
      if (five(row, col, 0, 1, color) ||
          five(row, col, 1, 0, color) ||
          five(row, col, -1, 1, color) ||
          five(row, col, -1, -1, color))
      {
         // 任意一个方向上出现了true也就是五星连珠，则设置返回值
         return color == chess_white ? white_id : black_id;
      }
      return 0;
   }

   uint64_t get_room_id()
   {
      return id;
   }

   int get_player_count()
   {
      return play_count;
   }

   room_status get_status()
   {
      return status;
   }


   void add_white_user(uint64_t uid)
   {
      white_id = uid;
      play_count++;
   }

   void add_black_user(uint64_t uid)
   {
      black_id = uid;
      play_count++;
   }

   uint64_t get_white_id()
   {
      return white_id;
   }

   uint64_t get_black_id()
   {
      return black_id;
   }

   ~room() 
   {
      DBG_LOG("%lu 房间销毁成功!!", id);
   }

   // 处理下棋，返回得是json数据，因为要广播
   Json::Value handle_chess(Json::Value &req)
   {
     Json::Value json_resp = req;
            // 2. 判断房间中两个玩家是否都在线，任意一个不在线，就是另一方胜利。
            int chess_row = req["row"].asInt();
            int chess_col = req["col"].asInt();
            uint64_t cur_uid = req["uid"].asUInt64();
            if (online_user->is_in_room(white_id) == false) {
                json_resp["result"] = true;
                json_resp["reason"] = "运气真好！对方掉线，不战而胜！";
                json_resp["winner"] = (Json::UInt64)black_id;
                board.clear();//棋盘滞空
                return json_resp;
            }
            if (online_user->is_in_room(black_id) == false) {
                json_resp["result"] = true;
                json_resp["reason"] = "运气真好！对方掉线，不战而胜！";
                json_resp["winner"] = (Json::UInt64)white_id;
                board.clear();//棋盘滞空
                return json_resp;
            }
            // 3. 获取走棋位置，判断当前走棋是否合理（位置是否已经被占用）
            if (board[chess_row][chess_col] != 0) {
                json_resp["result"] = false;
                json_resp["reason"] = "当前位置已经有了其他棋子！";
                return json_resp;
            }
            int cur_color = cur_uid == white_id ? chess_white : chess_black;
            board[chess_row][chess_col] = cur_color;
            // 4. 判断是否有玩家胜利（从当前走棋位置开始判断是否存在五星连珠）
            uint64_t winner_id = check_win(chess_row, chess_col, cur_color);
            if (winner_id != 0) {
                json_resp["reason"] = "五星连珠，战无敌！";
            }
            json_resp["result"] = true;
            json_resp["winner"] = (Json::UInt64)winner_id;
            board.clear();//棋盘滞空
            return json_resp;
        }
       
   

   // 处理聊天
   Json::Value handle_chat(Json::Value &req)
   {
      Json::Value resp = req;

      // 2.检查是否有敏感词汇
      std::string message = req["message"].asCString();
      std::string sen = "垃圾";
      int pos = message.find(sen);
      if (pos != std::string::npos)
      {
         resp["result"] = false;
         resp["reason"] = "包含敏感词汇";
         return resp;
      }
      resp["result"] = true;
      return resp;
   }
   // 处理用户退出-----这个不算在request处理之中！！！！
   void handle_exit(uint64_t uid)
   {
      DBG_LOG("有玩家退出了");
      //如果是下棋中退出，则对方胜利，否则下棋结束了退出，则是正常退出
            Json::Value json_resp;
            if (status == GAME_START) {
                uint64_t winner_id = (Json::UInt64)(uid == white_id ? black_id : white_id);
                json_resp["optype"] = "put_chess";
                json_resp["result"] = true;
                json_resp["reason"] = "对方掉线，不战而胜！";
                json_resp["room_id"] = (Json::UInt64)id;
                json_resp["uid"] = (Json::UInt64)uid;
                json_resp["row"] = -1;
                json_resp["col"] = -1;
                json_resp["winner"] = (Json::UInt64)winner_id;
                uint64_t loser_id = winner_id == white_id ? black_id : white_id;
                user_tb->win(winner_id);
                user_tb->lose(loser_id);
                status = GAME_OVER;
                board.clear();//棋盘滞空
                broadcoast(json_resp);
            }
            //房间中玩家数量--
            play_count--;
            return;
   }
   // 处理请求，主要就是上面的下棋和聊天
   void handle_request(Json::Value &req)
   {
       Json::Value resp = req;
      // 1.判断房间号是否符合！！！！！
      if (req["room_id"].asUInt64() != id)
      {
         resp["optype"] = "chat";
         resp["result"] = false;
         resp["reason"] = "房间号不匹配";
         broadcoast(resp);
      }
      // 2.判断
      if (req["optype"].asString() == "put_chess")//孩子记住，这里使用的是asString，不是asCString
      {
         resp = handle_chess(req);
         if (resp["winner"].asInt64() != 0)
         {
            // 有人胜利--需要改变数据库中的内容！！！！！！！
            uint64_t winner_id = resp["winner"].asUInt64();
            uint64_t lose_id = winner_id == white_id ? black_id : white_id;
            user_tb->win(winner_id);
            user_tb->lose(lose_id);
            board.clear();//棋盘滞空
            status = GAME_OVER;
         }
      }

      else if (req["optype"].asString() == "chat")
      {
         handle_chat(req);
      }

      else
      {
         resp["optype"] = req["optype"].asString();
         resp["result"] = false;
         resp["reason"] = "未知请求";
      }

      broadcoast(resp);
   }

   // 进行广播！！！！！！！！！！
   void broadcoast(Json::Value &rsp)
   {
      // 1.把Json::Value的数据转换为Json格式的字符串
      std::string body;
      json_util ::serialize(rsp, body);

      // 2.获取用户id对应的通信连接并使用send函数发送！！！！！，房间目前最多只有黑棋和白棋两个用户
      wsserver_t::connection_ptr white_conn = online_user->get_conn_from_room(white_id);
      if (white_conn.get() != nullptr)
      {
       //  DBG_LOG("房间-白棋玩家连接成功");
         white_conn->send(body);
      }
      else
      {
         DBG_LOG("房间-白棋玩家连接获取失败");
      }
      wsserver_t::connection_ptr black_conn = online_user->get_conn_from_room(black_id);
      if (black_conn.get() != nullptr)
      {
         //DBG_LOG("房间-黑棋玩家连接成功");
         black_conn->send(body);
      }
      else
      {
         DBG_LOG("房间-黑棋玩家连接获取失败");
      }
      return;
   }

};

using room_ptr = std::shared_ptr<room>;

class room_manager{
    private:
       uint64_t next_rid;
       std::mutex _mutex;
       user_table * user_tb;
       online_manager* online_user;
       std::unordered_map<uint64_t,room_ptr>_rooms;//绑定房间id和指针
       std::unordered_map<uint64_t,uint64_t>_users;//绑定房间id和两个用户id-------通过用户id先获取房间id
      
   public:
       //初始化房间计数器
       room_manager( user_table * ut,online_manager* om):
       next_rid(1),
       user_tb(ut),
       online_user(om)
       {
         DBG_LOG("初始化成功\n");
       }

       ~room_manager()
       {
          DBG_LOG("销毁成功\n");
       }

       //为两个用户创建房间，并返回房间的智能指针对象！！！！
       room_ptr create_room(uint64_t uid1,uint64_t uid2)
       {
         //1.判断两个用户是否在大厅内
         if(online_user->is_in_hall(uid1)  == false)
         {
            DBG_LOG("白棋不在大厅内");
            return room_ptr();//二者有一个不在就返回空指针
         }

         if(online_user->is_in_hall(uid2) == false)
         {
            DBG_LOG("黑棋不在大厅内");
            return room_ptr();//二者有一个不在就返回空指针
         }


         //2.创建房间并把客户数据放入----要加锁!!!!!!!!!!!!!!!!
         std::unique_lock<std::mutex> lock(_mutex);
         room_ptr rm (new room(next_rid,user_tb,online_user));
         DBG_LOG("新创建了一个房间,房间ID是：%d", next_rid);
         rm->add_white_user(uid1);
         rm->add_black_user(uid2);

         //3.管理房间数据，即初始化哈希表
         _rooms.insert(std::make_pair(next_rid,rm));
         _users.insert(std::make_pair(uid1,next_rid));
         _users.insert(std::make_pair(uid2,next_rid));


         next_rid++;//房间id要不断自增！！！！！！！！--------先插入再增加
         //4.返回房间指针
         return rm;
       }


       //通过房间rid获得房间的智能指针对象
       room_ptr get_room_by_rid(uint64_t rid)
       {
         std::unique_lock<std::mutex> lock(_mutex);
         DBG_LOG("_rooms 房间数量 : %ld", _rooms.size());

         for(auto e : _rooms)
         {
            std::cout << e.first << std::endl;
         }
          if(_rooms.find(rid) == _rooms.end())
          {
             return room_ptr();
          }

          return _rooms[rid];//////////////////////////////////////////////////
       }



       //通过用户uid获得房间的智能指针对象
       room_ptr get_room_by_uid(uint64_t uid)
       {
         //1.现在uers中找到房间id
         std::unique_lock<std::mutex> lock(_mutex);
         auto it = _users.find(uid);
         if( it == _users.end())
         {
            return room_ptr();
         }
         uint64_t rid = it->second;
         
         //2.再在rooms中找到room的智能指针对象！！！！！-----这里不能直接使用上面的get_room_by_id因为有互斥锁存在，有可能会造成死锁！！！！！
         auto rit = _rooms.find(rid);
         if( rit == _rooms.end())
         {
            return room_ptr();
         }
         return rit->second;
       }

       //通过房间id的销毁房间
       void remove_room(uint64_t rid)
       {
         //1.根据rid获取指针对象
         auto ptr = get_room_by_rid(rid);
        // DBG_LOG("房间是 %p". ptr.get());
         if(ptr.get() == nullptr)
         {
            DBG_LOG("房间没有找到 房间ID是%ld", rid);
            return ;
         }

          DBG_LOG("房间找到了");
         //2.获取这个房间的用户的id
         uint64_t uid1 = ptr->get_white_id();
         uint64_t uid2 = ptr->get_black_id();

         std::unique_lock<std::mutex> lock(_mutex);

         //3.删除user哈希表中用户id
         _users.erase(uid1);
         _users.erase(uid2);

         //4.删除_room哈希表中的uid
         _rooms.erase(rid);

         DBG_LOG("房间销毁成功");
       }

       //删除房间中的指定用户，如果房间中已经没有对象了，那么直接销毁房间，用户断开连接时不可使用！！！！！
       void remove_room_user(uint64_t uid)
       {
         
           //1.先查找是否有这个房间
            auto rp = get_room_by_uid(uid);
            if(rp.get() == nullptr)
            {
               return;
            }

            DBG_LOG("有玩家退出了");

            //2.处理玩家的退出请求！！！！！！！！！！！！！！
            rp->handle_exit(uid);

            //3.如果玩家数量为0，那么直接删除房间！！！！！！！
            if(rp->get_player_count() == 0)
            {
                DBG_LOG("没有玩家了,删除房间");
               remove_room(rp->get_room_id());
            }
            else
            {
               DBG_LOG("玩家数量是: %d", rp->get_player_count());
            }

       }
      
};

#endif